package com.xhs.config;

import com.xhs.util.LicenseValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.stream.Collectors;

/**
 * 卡密校验配置类
 * 在Spring应用启动时进行卡密校验
 * 
 * @author xhs
 */
@Component
@Order(1) // 设置最高优先级，确保在其他组件初始化前执行
public class LicenseCheckConfig implements CommandLineRunner {
    
    private static final Logger logger = LoggerFactory.getLogger(LicenseCheckConfig.class);
    private static final String SECRET_KEY = "XHS-LICENSE-2024-SECURE-KEY-FOR-GENERATION";
    
    @Value("${app.license-key:}")
    private String licenseKey;
    
    @Override
    public void run(String... args) throws Exception {
        if (licenseKey == null || licenseKey.isEmpty()) {
            System.exit(1);
        }
        
        // 1. Generate machine code in Java
        String machineCode = getMachineCode();

        // 2. Generate expected license key using machine code + SECRET_KEY
        String expectedLicenseKey = generateLicenseKey(machineCode);

        // 3. Validate license key
        if (!licenseKey.equalsIgnoreCase(expectedLicenseKey)) {
            System.exit(1);
        }
    }

    private String getMachineCode() {
        try {
            String os = System.getProperty("os.name").toLowerCase();
            if (!os.contains("win")) {
                throw new UnsupportedOperationException("License check is only supported on Windows.");
            }

            String cpuId = getCpuSerialNumber();
            String boardSerial = getMotherboardSerialNumber();
            String diskSerial = getHardDiskSerialNumber();
            String macAddress = getFirstMacAddress();
            String osInstallId = getOsInstallationId();
            
            String hardwareInfo = cpuId + "-" + boardSerial + "-" + diskSerial + "-" + macAddress + "-" + osInstallId;
            
            // Comprehensive cleanup: remove ALL whitespace characters (spaces, tabs, newlines, carriage returns, etc.)
            String cleanedHardwareInfo = hardwareInfo
                .replaceAll("\\s+", "")           // Remove all whitespace sequences
                .replaceAll("[\\r\\n\\t]", "")    // Remove any remaining newlines, tabs, carriage returns
                .replaceAll(" ", "");             // Final space removal verification
            
            // Generate SHA256 hash of cleaned hardware info
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(cleanedHardwareInfo.getBytes(StandardCharsets.UTF_8));
            
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            
            return hexString.toString();

        } catch (Exception e) {
            // In case of any error, we generate a stable fallback
            return "GENERATION_ERROR_" + System.getProperty("user.name", "UNKNOWN");
        }
    }
    
    private String generateLicenseKey(String machineCode) {
        try {
            String combinedData = machineCode + ":" + SECRET_KEY;
            
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(combinedData.getBytes(StandardCharsets.UTF_8));
            
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();

        } catch (Exception e) {
            return "LICENSE_GENERATION_ERROR";
        }
    }

    private String getCpuSerialNumber() {
        try {
            // Try PowerShell with Get-WmiObject
            String result = executePowerShellCommand("try { (Get-WmiObject -Class Win32_Processor | Select-Object -First 1).ProcessorId } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try PowerShell with Get-CimInstance
            result = executePowerShellCommand("try { (Get-CimInstance -ClassName Win32_Processor | Select-Object -First 1).ProcessorId } catch { '' }");
            if (!result.isEmpty()) return result;
            
            return "FALLBACK_CPU_" + System.getProperty("user.name", "UNKNOWN");
        } catch (Exception e) {
            return "FALLBACK_CPU_" + System.getProperty("user.name", "UNKNOWN");
        }
    }
    
    private String getMotherboardSerialNumber() {
        try {
            // Try PowerShell with Get-WmiObject SerialNumber
            String result = executePowerShellCommand("try { $board = (Get-WmiObject -Class Win32_BaseBoard | Select-Object -First 1).SerialNumber; if ($board -and $board -notmatch '^(ToBeFilledByO\\.E\\.M\\.|NotAvailable|Default.*|^\\\\s*$)') { $board } else { '' } } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try PowerShell with Get-CimInstance SerialNumber
            result = executePowerShellCommand("try { $board = (Get-CimInstance -ClassName Win32_BaseBoard | Select-Object -First 1).SerialNumber; if ($board -and $board -notmatch '^(ToBeFilledByO\\.E\\.M\\.|NotAvailable|Default.*|^\\\\s*$)') { $board } else { '' } } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try getting Product instead
            result = executePowerShellCommand("try { (Get-WmiObject -Class Win32_BaseBoard | Select-Object -First 1).Product } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try CimInstance Product
            result = executePowerShellCommand("try { (Get-CimInstance -ClassName Win32_BaseBoard | Select-Object -First 1).Product } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try Manufacturer
            result = executePowerShellCommand("try { (Get-WmiObject -Class Win32_BaseBoard | Select-Object -First 1).Manufacturer } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try Computer System Model
            result = executePowerShellCommand("try { (Get-WmiObject -Class Win32_ComputerSystem | Select-Object -First 1).Model } catch { '' }");
            if (!result.isEmpty()) return result;
            
            return "FALLBACK_BOARD_" + System.getProperty("user.name", "UNKNOWN");
        } catch (Exception e) {
            return "FALLBACK_BOARD_" + System.getProperty("user.name", "UNKNOWN");
        }
    }
    
    private String getHardDiskSerialNumber() {
        try {
            // Try PowerShell with Get-WmiObject
            String result = executePowerShellCommand("try { (Get-WmiObject -Class Win32_DiskDrive | Where-Object { $_.MediaType -like '*fixed*' } | Select-Object -First 1).SerialNumber } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try PowerShell with Get-CimInstance
            result = executePowerShellCommand("try { (Get-CimInstance -ClassName Win32_DiskDrive | Where-Object { $_.MediaType -like '*fixed*' } | Select-Object -First 1).SerialNumber } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try volume serial number
            try {
                Process process = Runtime.getRuntime().exec("cmd /c vol c:");
                process.getOutputStream().close();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String volumeInfo = reader.lines()
                                              .filter(line -> line.contains("Serial Number"))
                                              .findFirst()
                                              .orElse("");
                    if (!volumeInfo.isEmpty()) {
                        String[] parts = volumeInfo.split("\\s+");
                        for (int i = 0; i < parts.length; i++) {
                            if (parts[i].equals("Number") && i + 1 < parts.length) {
                                return parts[i + 1].trim();
                            }
                        }
                    }
                }
            } catch (Exception ignored) {}
            
            return "FALLBACK_DISK_" + System.getProperty("user.name", "UNKNOWN");
        } catch (Exception e) {
            return "FALLBACK_DISK_" + System.getProperty("user.name", "UNKNOWN");
        }
    }
    
    private String getFirstMacAddress() {
        try {
            // Try PowerShell with Get-WmiObject
            String result = executePowerShellCommand("try { (Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true -and $_.MACAddress } | Select-Object -First 1).MACAddress } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try PowerShell with Get-CimInstance
            result = executePowerShellCommand("try { (Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true -and $_.MACAddress } | Select-Object -First 1).MACAddress } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try getmac command
            try {
                Process process = Runtime.getRuntime().exec("getmac /fo table /nh");
                process.getOutputStream().close();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String macResult = reader.lines()
                                            .filter(line -> line.contains("-") && !line.contains("N/A"))
                                            .map(line -> line.split("\\s+")[0].trim())
                                            .findFirst()
                                            .orElse("");
                    if (!macResult.isEmpty()) return macResult;
                }
            } catch (Exception ignored) {}
            
            return "FALLBACK_MAC_" + System.getProperty("user.name", "UNKNOWN");
        } catch (Exception e) {
            return "FALLBACK_MAC_" + System.getProperty("user.name", "UNKNOWN");
        }
    }
    
    private String getOsInstallationId() {
        try {
            // Try PowerShell with Get-WmiObject
            String result = executePowerShellCommand("try { (Get-WmiObject -Class Win32_OperatingSystem).InstallDate } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try PowerShell with Get-CimInstance
            result = executePowerShellCommand("try { (Get-CimInstance -ClassName Win32_OperatingSystem).InstallDate } catch { '' }");
            if (!result.isEmpty()) return result;
            
            // Try registry
            try {
                Process process = Runtime.getRuntime().exec("reg query \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\" /v InstallDate");
                process.getOutputStream().close();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String regInfo = reader.lines()
                                          .filter(line -> line.contains("InstallDate"))
                                          .findFirst()
                                          .orElse("");
                    if (!regInfo.isEmpty()) {
                        String[] parts = regInfo.trim().split("\\s+");
                        if (parts.length >= 3) {
                            return parts[2];
                        }
                    }
                }
            } catch (Exception ignored) {}
            
            return "FALLBACK_OS_" + System.getProperty("user.name", "UNKNOWN");
        } catch (Exception e) {
            return "FALLBACK_OS_" + System.getProperty("user.name", "UNKNOWN");
        }
    }

    private String executePowerShellCommand(String command) throws Exception {
        Process process = Runtime.getRuntime().exec("powershell -ExecutionPolicy Bypass -Command \"" + command + "\"");
        process.getOutputStream().close();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            return reader.lines()
                         .filter(line -> !line.trim().isEmpty())
                         .findFirst()
                         .orElse("")
                         .trim();
        }
    }

    private String executeWmicValueCommand(String command, String property) throws Exception {
        Process process = Runtime.getRuntime().exec("wmic " + command + " /value");
        process.getOutputStream().close();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            return reader.lines()
                         .filter(line -> line.startsWith(property + "="))
                         .map(line -> line.substring(property.length() + 1).trim())
                         .filter(value -> !value.isEmpty())
                         .findFirst()
                         .orElse("");
        }
    }

    private String executeWmicCommand(String command) throws Exception {
        Process process = Runtime.getRuntime().exec("wmic " + command);
        process.getOutputStream().close();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            return reader.lines()
                         .filter(line -> !line.trim().isEmpty())
                         .filter(line -> !line.contains(command.split(" ")[1])) // Filter out header
                         .map(String::trim)
                         .collect(Collectors.joining())
                         .replaceAll("\\s+", "");
        }
    }
} 